package com.legstar.eci.xmi;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.StringWriter;

import org.apache.commons.io.FilenameUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.ws.commons.schema.XmlSchema;
import org.w3c.dom.Document;

import com.legstar.dom.DocumentFactory;
import com.legstar.dom.InvalidDocumentException;
import com.legstar.eci.xmi.model.WsdlBinding;
import com.legstar.eci.xmi.model.WsdlDefinitions;
import com.legstar.eci.xmi.model.WsdlImport;
import com.legstar.eci.xsd.EciXsdAnnotator;
import com.legstar.xsd.XsdMappingException;
import com.legstar.xsd.XsdNavigator;
import com.legstar.xsd.XsdObjectProcessor;
import com.legstar.xsd.XsdReader;

/**
 * API for ECI XMI WSDL translation.
 * <p/>
 * ECI WSDL files are built from a main WSDL file which contains the ECI/XMI
 * binding elements and a separated, imported, pure WSDL that describes the
 * schema types and operations.
 * <p/>
 * Here we build a java model from the main ECI/XMI model. That model will hold
 * the ECI/XMI meta data.
 * <p/>
 * We then use the imported pure WSDL to get the structure of the target XML
 * schema.
 * <p/>
 * The target XML schema will therefore be very similar to the xsd:types element
 * of the imported wsdl, with additional LegStar COBOL annotations derived from
 * the ECI/XMI meta data.
 * 
 * 
 */
public class EciXmiToXsd {

    /** Logger. */
    private final Log _log = LogFactory.getLog(getClass());

    /** When XSD files are created, we will use this encoding. */
    public static final String OUTPUT_XSD_ENCODING = "UTF-8";

    /**
     * Execute the translation from ECI WSDL to LegStar XML Schema.
     * 
     * @param eciWsdlFile the file containing ECI XMI wsdl
     * @return the XML Schema as a string
     * @throws XsdMappingException if XML schema generation process fails
     */
    public String translate(final File eciWsdlFile) throws XsdMappingException {
        return translate(eciWsdlFile, (String) null);
    }

    /**
     * Execute the translation from ECI WSDL to LegStar XML Schema.
     * <p/>
     * 
     * @param eciWsdlFile the file containing ECI XMI wsdl
     * @param sourceEncoding the character set used to encode the ECI XMI wsdl
     *            source file
     * @return the XML Schema as a string
     * @throws XsdMappingException if XML schema generation process fails
     */
    public String translate(final File eciWsdlFile, final String sourceEncoding)
            throws XsdMappingException {

        return toString(toXmlSchema(eciWsdlFile, sourceEncoding));
    }

    /**
     * Execute the translation from ECI WSDL to LegStar XML Schema.
     * 
     * @param eciWsdlFile the file containing ECI XMI wsdl
     * @return the XML Schema
     * @throws XsdMappingException
     * @throws XsdMappingException if XML schema generation process fails
     */
    public XmlSchema toXmlSchema(final File eciWsdlFile)
            throws XsdMappingException {
        return toXmlSchema(eciWsdlFile, null);
    }

    /**
     * Execute the translation from ECI WSDL to LegStar XML Schema.
     * <p/>
     * 
     * @param eciWsdlFile the file containing ECI XMI wsdl
     * @param sourceEncoding the character set used to encode the ECI XMI wsdl
     *            source file
     * @return the XML Schema
     * @throws XsdMappingException if XML schema generation process fails
     */
    public XmlSchema toXmlSchema(final File eciWsdlFile,
            final String sourceEncoding) throws XsdMappingException {
        try {
            /* Sanity check. */
            if (eciWsdlFile == null || !eciWsdlFile.exists()) {
                throw new FileNotFoundException((eciWsdlFile == null) ? "null"
                        : eciWsdlFile.toString());
            }

            /* Build a java model from the ECI XMI meta data */
            EciXmiReader reader = new EciXmiReader();
            WsdlDefinitions wsdlDefinitions = (WsdlDefinitions) reader.read(
                    eciWsdlFile, sourceEncoding);

            /* Make sure it contains ECI XMI meta data */
            if (wsdlDefinitions.getBindings().size() == 0) {
                throw new IOException("File " + eciWsdlFile
                        + " does not contain ECI XMI meta data");
            }
            WsdlBinding wsdlBinding = wsdlDefinitions.getBindings().get(0);

            /* Lookup the imported WSDL file location */
            String wsdlLocation = null;
            for (WsdlImport wsdlImport : wsdlDefinitions.getImports()) {
                wsdlLocation = wsdlImport.getLocation();
            }
            if (wsdlLocation == null) {
                throw new IOException("Unable to locate imported wsdl in "
                        + eciWsdlFile);
            }

            /*
             * If location is relative, consider it is relative to the main WSDL
             * file
             */
            if (FilenameUtils.getPrefix(wsdlLocation).length() == 0) {
                String baseDir = FilenameUtils.getFullPath(eciWsdlFile
                        .getAbsolutePath());
                wsdlLocation = baseDir + wsdlLocation;
            }
            /* Read the imported WSDL */
            XmlSchema schema = parse(wsdlLocation);

            /* Now visit the wsdl elements and annotate */
            XsdObjectProcessor annotator = new EciXsdAnnotator(wsdlBinding);
            annotator.setUp();
            XsdNavigator navigator = new XsdNavigator(schema, annotator);
            navigator.visit();
            return schema;
        } catch (IOException e) {
            throw new XsdMappingException(e);
        }
    }

    /**
     * Parse a file and generate an XML Schema.
     * 
     * @param fileName the input file (an XSD or WSDL)
     * @return an XML schema
     * @throws XsdMappingException if something fails
     */
    protected XmlSchema parse(final String fileName) throws XsdMappingException {
        try {
            Document doc = DocumentFactory.parse(new File(fileName));
            return XsdReader.read(doc);
        } catch (InvalidDocumentException e) {
            throw new XsdMappingException(e);
        } catch (Exception e) {
            throw new XsdMappingException(e);
        }
    }

    /**
     * Prints an XML schema in a String.
     * 
     * @param schema the XML schema
     * @return the content as a string
     */
    public String toString(final XmlSchema schema) {
        StringWriter writer = new StringWriter();
        schema.write(writer);
        String result = writer.toString();
        if (_log.isDebugEnabled()) {
            _log.debug(result);
        }
        return result;
    }

}
